/********************************************************************************

* @File name: ftm.c

* @Author: Pan Zhimin

* @Version: 1.0

* @Date: 2023-8-21

* @Description: FTM SA & FA TEST

******************************************************************************* */

#include "ftm.h"

/* 本地变量 */
const devinfo_t default_devinfo = {
	.ble_uuid = BLE_UUID_DEFAULT,			// 直接用宏定义数组的话, 编译会报错
	.ble_major = BLE_MAJOR_DEFAULT,			// app应用就此判断FTM文件是否有效
	.ble_minor = BLE_MINOR_DEFAULT,

	.sim_num = {0},
	.sim_len = 0,

	.bootmode = MODE_SA, 					// 默认是SA状态

	.sn = DEFAULT_SN_DEVICE_ID_VAL,
	.sn_len = 0,

	.flag = DEVINFO_NV_FLAG,

#if HAS_OTA_URL
	.ota_url_len = 0,
#endif

#if HAS_NTC_CAL
	.adc_value = 0,
#endif

};

static devinfo_t g_ftm_devinfo = { 0 };
static uint16_t receive_length = 0;
static uint8_t receive_buffer[RECV_BUF_SIZE] = { 0 };

static ql_sem_t semaphore_handler = NULL;

static bool g_wifi_status = false;
static int8_t g_gprs_rssi = BASE_RSSI_MIN;

static bool g_ftm_http_upload_success = false;
static uint8_t g_ftm_sync_rsp = 0;

#if HAS_485_UART
static uart_485_t g_uart_485[2] = { {true, false}, {false, false} };	// 暂定支持两路485测试		
#endif

#if HAS_UUID
// 原文链接：https://blog.csdn.net/weixin_37592723/article/details/94733470	
ql_bleadv_set_data_s ftm_ble_adv_data = {
	.date_len = 9,
	.data = {0x02, 0x01, 0x05, 0x1A, 0xFF, 0x4c, 0x00, 0x02, 0x15},   // 后续打开广播时,增加 UUID,MAJOR,MINOR,POWER
};

ql_bt_ble_local_name_s ftm_ble_name = {
	.name = AKM_BLE_NAME,
	.code_type = QL_BT_BLE_NAME_CODE_UTF8,
};

ql_bleadv_param_s ftm_ble_adv_param = {
	.min_adv = QL_BLE_ADV_MIN_ADV,
	.max_adv = QL_BLE_ADV_MAX_ADV,
	.adv_type = QL_BLEADV_NONCONN_IND,
	.own_addr_type = QL_BLE_PUBLIC_ADDRESS,
	.channel_map = QL_BLE_ADV_MAP,
	.filter = QL_BLEADV_FILTER_0,
	.discov_mode = 2,
	.no_br_edr = 1,
	.enable_adv = 1,
};
#endif

/****************************static functions********************************/
static uint16_t get_u16(uint8_t* data) {
	return (data[0] << 8) | data[1];
}

static bool pkt_valid(uint8_t* data) {
	uint8_t pkt_prefix[] = PKT_PREFIX;

	if (memcmp(data, pkt_prefix, sizeof(pkt_prefix)) != 0) {
		return false;
	}

	uint16_t cmd = get_u16(&data[8]) & 0xf000;

	return (cmd == FTM_MASTER_CMD_PREFIX) || (cmd == FTM_SLAVE_CMD_PREFIX);
}

static uint8_t ftm_pkt_sum(uint8_t* data, uint32_t size) {
	uint8_t s = 0;
	for (uint32_t i = 0; i < size; i++) {
		s += data[i];
	}
	return s;
}

static uint8_t business_pkt_sum(uint8_t* data, uint32_t size) {
	uint8_t s = 0;
	for (uint32_t i = 0; i < size; i++) {
		s ^= data[i];
	}
	return s;
}

/*
* Description: 	解析数据包, 转成pkt格式
* Parameter:
* @data:		数据内容
* @rx_len:		数据长度
* Return：		&pkt, -1:clear, 0: continue, 1:OK
* Others:		正常的pc过来的数据包
*/
static int8_t get_pc_pkt(uint8_t* data, uint32_t rx_len, pkt_t* pkt) {
	uint8_t pkt_prefix[] = PKT_PREFIX;

	if (rx_len && data[0] != pkt_prefix[0]) {
		return -1;
	}

	if (rx_len < PKT_HEAD_SZ) {
		return 0;
	}

	if (!pkt_valid(data)) {
		return -1;
	}

	//     #define     PKT_HEAD_SZ  (sizeof(PKT_PREFIX) + 2 /* cmd(0xaabb) */ + 1 /* data_len(0xcc) */)
	uint32_t pkt_len = PKT_HEAD_SZ /* 8 + 2 + 1 */ + data[10] + 2 /* checksum(0xdd) + suffix(0xBB) */;


	// AA 00 FF FF FF FF FF FF 81 01 [0C] [48 44 36 31 39 30 35 39 43 30 30 31] D0 BB
	// PKT_HEAD_SZ = 11,	data_len = data[10] = 0x0C, 		pkt_len = 11 + 0x0c + 2 = 25

	if (rx_len < pkt_len || data[pkt_len - 1] != PKT_SUFFIX) {
		/* 1, 收到的长度 比 应该的长度要少 */
		/* 2, 收到的尾巴 不是 0xBB */
		// 都需要继续接收uart数据
		// FTM_LOG("1.1, rx_len:%d < data[10]:%d", rx_len, pkt_len);
		// FTM_LOG("1.2, data[pkt_len - 1]: 0x%02x != 0xBB ?", data[pkt_len - 1]);
		// FTM_LOG("1.3, receive data continue");
		return 0;
	}

	if (ftm_pkt_sum(data, pkt_len - 2) != data[pkt_len - 2]) {
		/* 3, 这里数据都收全了, 但checkSum不对, 则需要clear, 否则解析pkt永远为NULL, 不能再执行其他命令了 */
		// FTM_LOG("2.0, check sum:0x%02x != data[len-1]:0x%02x, clear rx_buffer", ftm_pkt_sum(data, pkt_len - 2), data[pkt_len - 2]);
		return -1;
	}

	pkt->cmd = get_u16(&data[8]);
	pkt->data_len = data[10];
	pkt->data = &data[PKT_HEAD_SZ];

	return 1;
}

/*
* Description: 	解析数据包, 转成pkt格式
* Parameter:
* @data:		数据内容
* @rx_len:		数据长度
* Return：		&pkt, -1:clear, 1:OK
* Others:		Cat1和M0业务数据包
* Note:			IotResponse(澳柯玛项目): 低字节在前, 高字节在后
				{AA, 07, 00, 01, D1, 7D, 00}
*/
static int8_t get_business_pkt(uint8_t* data, uint32_t rx_len, pkt_t* pkt) {
#if (CFG_DEVICE_TYPE == 24002 || CFG_DEVICE_TYPE == 24003)
	// 老的单片机程序已经固化, 通信协议同海尔系列
	uint16_t pkt_len = 0;
	if (rx_len >= 5 && (pkt_len = ((data[1] << 8) | data[2])) >= 5) {
		if (rx_len >= pkt_len && data[0] == 0xAA) {
			if (business_pkt_sum(data, pkt_len) == 0) {
				pkt->cmd = data[3];
				pkt->data = &data[4];
				pkt->data_len = pkt_len - 5;	// 5: 1 /*head*/ + 2 /*len*/ + 1 /*cmd*/ + 1 /*checksum*/
				return 1;
			}
		}
	}
#else
	ZqCommPkg* pkg = (ZqCommPkg*)data;
	if (rx_len >= pkg->len && pkg->flag == 0xAA) {
		if (business_pkt_sum(data, pkg->len) == 0) {
			pkt->cmd = pkg->cmd;
			pkt->data_len = pkg->len - sizeof(ZqCommPkg);
			pkt->data = pkg->data;
			return 1;
		}
	}
#endif

	return -1;
}

/*
* Description: 	发送包给xx端
* Parameter:
* @uart_num:	目标(0: master, 1: slave)
* @cmd:			命令ID
* @data:		数据内容 (FTM格式)
* @size:		数据长度
* Return：
* Others:
*/
static void ftm_pkt_send(uint8_t port, uint16_t cmd, uint8_t* data, uint8_t size) {
	uint8_t tx_buffer[64 * 3];
	uint8_t pkt_prefix[] = PKT_PREFIX;
	uint16_t off = sizeof(pkt_prefix);

	memcpy(tx_buffer, pkt_prefix, off);

	tx_buffer[off++] = ((cmd >> 8) & 0xff);
	tx_buffer[off++] = cmd & 0xff;
	tx_buffer[off++] = size;

	if (data && size) {
		memcpy(&tx_buffer[off], data, size);
		off += size;
	}

	tx_buffer[off++] = ftm_pkt_sum(tx_buffer, PKT_HEAD_SZ + size);
	tx_buffer[off++] = PKT_SUFFIX;

	FTM_HEX_BUF_LOG("tx_buffer", tx_buffer, off);

#if HAS_485_UART && FTM_485_CTL
	if (port == FTM_PC_UART_PORT) {
		ql_gpio_set_level(FTM_485_GPIO, LVL_LOW);
		FTM_LOG("HAS_485_UART, set FTM_485_GPIO = LOW, BEFORE WRITE");
	}
#endif

	ql_uart_write(port, tx_buffer, off);
}

/*
* Description: 	发送包给M0端
* Parameter:
* @cmd:			命令ID
* @data:		数据内容 (业务格式)
* @size:		数据长度
* Return：
* Others:		pkt_send(澳柯玛项目): 低字节在前, 高字节在后
*/
static void businiss_pkt_send(uint16_t cmd, uint8_t* data, uint8_t size) {
	uint8_t tx_buffer[64 * 4];
	uint16_t off = 0;

#if (CFG_DEVICE_TYPE == 24002 || CFG_DEVICE_TYPE == 24003)
	// 老的单片机程序已经固化, 通信协议同海尔系列
	tx_buffer[off++] = 0xAA;
	tx_buffer[off++] = (size + 5) >> 8;
	tx_buffer[off++] = (size + 5) & 0xff;
	tx_buffer[off++] = cmd;
	if (size) {
		memcpy(&tx_buffer[off], data, size);
		off += size;
	}
	tx_buffer[off++] = business_pkt_sum(tx_buffer, 4 + size);
#else
	// 0x17: AA 05 B8 16 01, AA 05 B8 15 02
	if (size) {
		memcpy(tx_buffer + sizeof(ZqCommPkg), data, size);
	}
	ZqCommPkg* pkg = (ZqCommPkg*)tx_buffer;
	pkg->flag = 0xAA;
	pkg->len = sizeof(ZqCommPkg) + size;
	pkg->cmd = cmd;
	pkg->checksum = 0;
	pkg->checksum = business_pkt_sum(tx_buffer, pkg->len);
	off = sizeof(ZqCommPkg) + size;
#endif

	FTM_HEX_BUF_LOG("tx_buffer", tx_buffer, off);

	ql_uart_write(FTM_MCU_UART_PORT, tx_buffer, off);
}

static int32_t ftm_devinfo_load_from_nv() {
	devinfo_t data;

	QFILE fd = 0;
	int32_t ret = -1;

	fd = ql_fopen("UFS:ftm", "rb");
	if (fd <= 0) {
		return ret;
	}

	ret = ql_fseek(fd, 0, SEEK_SET);
	if (ret < 0) {
		FTM_LOG("ql_fseek FAIL");
		goto end;
	}

	// 不能判断长度而退出(会restore), 以防devinfo_t参数个数有变化 !
	int len = ql_fread((uint8_t*)&data, sizeof(data), 1, fd);

	ret = -1;
	if (data.flag == DEVINFO_NV_FLAG) {
		// FTM_LOG("FTM FILE struct flag same as 0x%x, !!! EFFECT !!!", DEVINFO_NV_FLAG);
		memcpy(&g_ftm_devinfo, &data, /*sizeof(data)*/len);	// 这里只copy已读到的长度 !
		ret = 0;
	} else {
		FTM_LOG("data.flag != DEVINFO_NV_FLAG, do not memcpy to g_ftm_devinfo");
	}

end:
	ql_fclose(fd);
	return ret;
}

static void devinfo_store(void) {
	QFILE fd = -1;
	uint8_t* p = NULL;

	devinfo_t data;

	fd = ql_fopen("UFS:ftm", "wb+");
	if (fd > 0) {
		memcpy(&data, &g_ftm_devinfo, sizeof(data));

		p = (uint8_t*)(&data);
		DELAYms(1);
		ql_dev_feed_wdt();
		if (NULL != p) {
			ql_fwrite(p, sizeof(data), 1, fd);
		}
	}

	ql_fclose(fd);
}

static void devinfo_load(void) {
	if (ftm_devinfo_load_from_nv() != 0) {
		FTM_LOG_ERR("LOAD FAIL, !!! SET FTM FILE TO DEFAULT VALUE !!!");
		memcpy(&g_ftm_devinfo, &default_devinfo, sizeof(default_devinfo));
		// Note: 除了生产命令, 不能去修改文件
		// devinfo_store();
	} else {
		// FTM_LOG_ERR("LOAD SUCCESS !!!");
	}
}

static bool is_ftm_mode(void) {
	if (g_ftm_devinfo.bootmode == 0) {
		devinfo_load();
	}
	return g_ftm_devinfo.bootmode < MODE_WK;
}

#if HAS_485_UART || HAS_NTC_CAL
static bool is_ftm_fa_mode(void) {
	if (g_ftm_devinfo.bootmode == 0) {
		devinfo_load();
	}
	return g_ftm_devinfo.bootmode == MODE_FA;
}
#endif

/****************************SA functions**************************************/

#ifdef FTM_M0_INFO
/*
* Description: 	解析M0的基本信息
* Parameter:
* @data:		数据
* @data_len:	数据长度
* @buf: 		返回要填入的值
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_cmd01(uint8_t* data, uint8_t data_len, uint8_t* buf) {
	uint8_t off = 1/*bootmode*/ + 16/*uuid*/ + 1/*major*/ + 1/*minor*/;
	uint8_t sn_len = data[off++];
	off += sn_len;

	uint8_t sw_len = data[off++];
	memcpy(buf, &data[off], sw_len);

	return sw_len;
}
#endif

/*
* Description: 	读SN号
* Parameter:
* @buf: 		返回要填入的值(如果是未分配或默认值, 则为0xff)
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sn_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (g_ftm_devinfo.sn_len <= 0 || memcmp(g_ftm_devinfo.sn, DEFAULT_SN_DEVICE_ID_VAL, strlen(DEFAULT_SN_DEVICE_ID_VAL)) == 0) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = g_ftm_devinfo.sn_len;
		strcpy((char*)buf, (char*)g_ftm_devinfo.sn);
	}

	FTM_LOG("READ: sn(%s), len(0x%02x - %d)", (data_len == 1 ? "Not Set" : (char*)buf), data_len, data_len);

	return data_len;
}

/*
* Description: 	写SN号
* Parameter:
* @buf: 		SN内容
* @sn_len:		SN长度
* Return：
* Others:
*/
static bool ftm_sa_sn_write(uint8_t* buf, int32_t sn_len) {
	bool result = false;

	if (buf && sn_len > 0) {
		g_ftm_devinfo.sn_len = sn_len;
		memcpy(g_ftm_devinfo.sn, buf, sn_len);
		g_ftm_devinfo.sn[sn_len] = 0;

		FTM_LOG("WRITE: sn(%s), len(0x%02x - %d)", g_ftm_devinfo.sn, sn_len, sn_len);

		devinfo_store();

		result = true;
	}

	return result;
}

/*
* Description: 	读硬件版本号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_hw_read(uint8_t* buf) {
	uint8_t data_len = 0;

#ifdef HARDWARE_VERSION
	strcpy((char*)buf, HARDWARE_VERSION);
#elif (defined ZQ_CAT1_HW_VER && defined ZQ_VER_PREFIX)
	sprintf((char*)buf, "%sH_V%s", ZQ_VER_PREFIX, ZQ_CAT1_HW_VER);
#else
	buf[data_len++] = RSP_NOT_SET;
#endif

	data_len = strlen((char*)buf);

	FTM_LOG("READ: hw(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

/*
* Description: 	读软件版本号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sw_read(uint8_t* buf) {
	uint8_t data_len = 0;

	uint8_t sw[100] = { 0 };
	if (!buf) {
		buf = sw;
	}

	ql_dev_get_firmware_version((char*)buf, 100);

#ifdef SOFTWARE_VERSION
	sprintf((char*)(buf + strlen((char*)buf)), "-%s", SOFTWARE_VERSION);
#elif (defined ZQ_CAT1_SW_VER && defined ZQ_VER_PREFIX)
	sprintf((char*)(buf + strlen((char*)buf)), "-%sS_V%s", ZQ_VER_PREFIX, ZQ_CAT1_SW_VER);
#else
	buf[data_len++] = RSP_NOT_SET;
#endif

	data_len = strlen((char*)buf);

	if (strlen((char*)sw)) {
		FTM_LOG("FTM START (%s)============, pid = %d, mode = %ld(%s)", sw, CFG_DEVICE_TYPE, g_ftm_devinfo.bootmode, g_ftm_devinfo.bootmode <= MODE_SA ? "SA" : (g_ftm_devinfo.bootmode <= MODE_FA ? "FA" : "WORK"));
	} else {
		FTM_LOG("READ: sw(%s), len(0x%02x - %d)", buf, data_len, data_len);
	}

	return data_len;
}

#if HAS_UUID
/*
* Description: 	读ble(uuid/major/minor)
* Parameter:
* @buf: 		返回要填入的值(如果是未分配或默认值, 则为0xff)
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_ble_read(uint8_t* buf) {
	uint8_t data_len = 0;

	uint8_t uuid_default[] = BLE_UUID_DEFAULT;
	int uuid_len = sizeof(uuid_default);

	if (/*memcmp(g_ftm_devinfo.ble_uuid, uuid_default, uuid_len) == 0 || */
		g_ftm_devinfo.ble_major == BLE_MAJOR_DEFAULT
		|| g_ftm_devinfo.ble_minor == BLE_MINOR_DEFAULT) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		memcpy(buf + data_len, g_ftm_devinfo.ble_uuid, uuid_len);
		data_len += uuid_len;

		buf[data_len++] = (g_ftm_devinfo.ble_major >> 8) & 0xff;
		buf[data_len++] = g_ftm_devinfo.ble_major & 0xff;

		buf[data_len++] = (g_ftm_devinfo.ble_minor >> 8) & 0xff;
		buf[data_len++] = g_ftm_devinfo.ble_minor & 0xff;
	}

	FTM_HEX_BUF_LOG("BLE uuid", g_ftm_devinfo.ble_uuid, sizeof(g_ftm_devinfo.ble_uuid));
	FTM_LOG("READ: ble(0x%x, 0x%x), len(0x%02x - %d)", g_ftm_devinfo.ble_major, g_ftm_devinfo.ble_minor, data_len, data_len);

	return data_len;
}

/*
* Description: 	写ble(uuid/major/minor)
* Parameter:
* @buf: 		内容
* @sn_len:		长度
* Return：
* Others:
*/
static bool ftm_sa_ble_write(uint8_t* buf, int32_t ble_len) {
	bool result = false;

	uint8_t uuid_default[] = BLE_UUID_DEFAULT;
	int uuid_len = sizeof(uuid_default);

	if (buf && ble_len >= (uuid_len + sizeof(g_ftm_devinfo.ble_major) + sizeof(g_ftm_devinfo.ble_minor))) {
		memcpy(g_ftm_devinfo.ble_uuid, buf, uuid_len);
		g_ftm_devinfo.ble_major = (buf[uuid_len] << 8) | (buf[uuid_len + 1] & 0xff);
		g_ftm_devinfo.ble_minor = (buf[uuid_len + 2] << 8) | (buf[uuid_len + 3] & 0xff);

		FTM_HEX_BUF_LOG("BLE uuid", g_ftm_devinfo.ble_uuid, sizeof(g_ftm_devinfo.ble_uuid));

		FTM_LOG("WRITE: ble(0x%x, 0x%x), len(0x%02x - %d)", g_ftm_devinfo.ble_major, g_ftm_devinfo.ble_minor, ble_len, ble_len);

		devinfo_store();

		result = true;
	}

	return result;
}
#endif

/*
* Description:  读Cat1启动模式
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_bootmode_read(uint8_t* buf) {
	uint8_t data_len = 0;

	buf[data_len++] = g_ftm_devinfo.bootmode;

	FTM_LOG("READ: bootMode(%d))", g_ftm_devinfo.bootmode);

	return data_len;
}

/*
* Description: 	写Cat1启动模式
* Parameter:
* @bootmode: 	启动模式
* Return：
* Others:
*/
bool ftm_bootmode_write(uint8_t bootmode, bool reboot) {
	if (bootmode < MODE_SA || bootmode > MODE_WK) {
		return false;
	}

	g_ftm_devinfo.bootmode = bootmode;
	devinfo_store();

	FTM_LOG("WRITE: bootMode(%d)", g_ftm_devinfo.bootmode);

	if (reboot) {
		DELAYms(2 * 1000);
		ql_power_reset(RESET_NORMAL);
		DELAYms(60 * 1000);
	}

	return true;
}

/*
* Description: 	读sim卡iccid号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sim_iccid_read(uint8_t* buf) {
	uint8_t data_len = 0;

	int result = 0;
	int retry_times = 0;

	while ((result = ql_sim_get_iccid(SIM_INDEX, (char*)buf, ICCID_LEN)) != QL_SIM_SUCCESS && retry_times++ < 9) {
		FTM_LOG_ERR("ql_sim_get_iccid FAIL, result = %d, retry_times = %d", result, retry_times);
		DELAYms(1 * 1000);
	}

	if (result != QL_SIM_SUCCESS) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = strlen((char*)buf);
	}

	FTM_LOG("READ: iccid(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

/*
* Description: 	读sim卡number号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sim_num_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (g_ftm_devinfo.sim_len <= 0) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = g_ftm_devinfo.sim_len;
		strcpy((char*)buf, (char*)g_ftm_devinfo.sim_num);
	}

	FTM_LOG("READ: num(%s), len(0x%02x - %d)", (data_len == 1 ? "Not Set" : (char*)buf), data_len, data_len);

	return data_len;
}

/*
* Description: 	写sim卡number号
* Parameter:
* @buf: 		SN内容
* @sn_len:		SN长度
* Return：
* Others:
*/
static bool ftm_sa_sim_num_write(uint8_t* buf, int32_t sim_len) {
	bool result = false;

	if (buf && sim_len > 0) {
		g_ftm_devinfo.sim_len = sim_len;
		memcpy(g_ftm_devinfo.sim_num, buf, sim_len);;
		g_ftm_devinfo.sim_num[sim_len] = 0;

		FTM_LOG("WRITE: num(%s), len(0x%02x - %d)", g_ftm_devinfo.sim_num, sim_len, sim_len);

		devinfo_store();

		result = true;
	}

	return result;
}

/*
* Description: 	读sim卡imsi号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sim_imsi_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (ql_sim_get_imsi(0, (char*)buf, IMSI_LEN) != QL_SIM_SUCCESS) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = strlen((char*)buf);
	}

	FTM_LOG("READ: imsi(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

/*
* Description: 	读sim卡imei号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_imei_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (ql_dev_get_imei((char*)buf, IEMI_LEN, SIM_INDEX) != QL_DEV_SUCCESS) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = strlen((char*)buf);
	}

	FTM_LOG("READ: imei(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

#if HAS_SPI
static bool ftm_sa_spi(void) {
	bool result = false;
	QFILE fd = -1;

	char w_data[7] = "123456";
	char r_data[7] = "";

	if ((fd = ql_fopen(FTM_SPI_FILENAME, "wb+")) > 0) {
		if (ql_fwrite(w_data, sizeof(w_data), 1, fd) == sizeof(w_data)) {
			result = true;
		}
		ql_fclose(fd);
	}

	if (result) {
		result = false;
		if ((fd = ql_fopen(FTM_SPI_FILENAME, "r")) > 0) {
			ql_fseek(fd, 0, SEEK_SET);
			if (ql_fread((uint8_t*)&r_data, sizeof(r_data), 1, fd) == sizeof(r_data) || !strcmp(w_data, r_data)) {
				result = true;
			}
			ql_fclose(fd);
		}
	}

	return result;
}
#endif

#if HAS_NTC_CAL
static bool ftm_sa_ntc_cal(void) {
	int adc_value;
	int adc_value_total = 0;

	for (int i = 0; i < 5; i++) {
		ql_adc_get_volt(0, &adc_value);
		adc_value_total += adc_value;
		DELAYms(20);
	}

	g_ftm_devinfo.adc_value = adc_value_total / 5;
	devinfo_store();

	FTM_LOG("WRITE: adc_value(%d)", g_ftm_devinfo.adc_value);

	return true;
}

int ftm_get_adc_value() {
	if (g_ftm_devinfo.adc_value == 0) {
		devinfo_load();
	}

	FTM_LOG("READ: adc_value(%d)", g_ftm_devinfo.adc_value);

	return g_ftm_devinfo.adc_value;
}
#endif

/*
* Description: 	读wifi & bt 地址
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:		wifi[5]减1就变成bt地址
*/
static uint8_t ftm_sa_wifi_bt_mac_read(uint8_t* buf) {
	uint8_t data_len = 0;

	char imei[IEMI_LEN + 1] = { 0 };
	char mac_addr[6] = { 0 };

	ql_dev_get_imei(imei, IEMI_LEN, SIM_INDEX);

	mac_addr[0] = (((imei[3] & 0xf) << 4) | (imei[4] & 0xf)) ^ 0xff;
	mac_addr[1] = (((imei[5] & 0xf) << 4) | (imei[6] & 0xf)) ^ 0xff;
	mac_addr[2] = (((imei[7] & 0xf) << 4) | (imei[8] & 0xf)) ^ 0xff;
	mac_addr[3] = (((imei[9] & 0xf) << 4) | (imei[10] & 0xf)) ^ 0xff;
	mac_addr[4] = (((imei[11] & 0xf) << 4) | (imei[12] & 0xf)) ^ 0xff;
	mac_addr[5] = ((((imei[13] & 0xf) << 4) | (imei[14] & 0xf)) ^ 0xff) + 1;

	// wifi
	memcpy(buf, mac_addr, sizeof(mac_addr));
	data_len += sizeof(mac_addr);

	// bt
#ifdef CONFIG_QUEC_PROJECT_FEATURE_BT
	ql_bt_addr_s bt_mac_addr;
	ql_bt_ble_get_nv_addr(&bt_mac_addr);
	memcpy(buf + sizeof(mac_addr), bt_mac_addr.addr, sizeof(bt_mac_addr.addr));
	data_len += sizeof(bt_mac_addr.addr);
#else 	
	mac_addr[5] -= 1;
	memcpy(buf + sizeof(mac_addr), mac_addr, sizeof(mac_addr));
	data_len += sizeof(mac_addr);
#endif

	FTM_LOG("READ: addr(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

#if HAS_OTA_URL
static uint8_t ftm_sa_ota_url_read(uint8_t* buf) {
	uint8_t data_len = 0;

	//"http://ota.origin-tech.cn:9944/api/device/version/dtu"
	if (g_ftm_devinfo.ota_url_len <= 0) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = g_ftm_devinfo.ota_url_len;
		memcpy(buf, g_ftm_devinfo.ota_url, data_len);
		buf[data_len++] = (g_ftm_devinfo.ota_port >> 8) & 0xff;
		buf[data_len++] = (g_ftm_devinfo.ota_port) & 0xff;
	}

	FTM_LOG("READ: url(%s), port(%d), len(0x%02x - %d)", (data_len == 1 ? "Not Set" : (char*)g_ftm_devinfo.ota_url), g_ftm_devinfo.ota_port, data_len, data_len);

	return data_len;
}

static bool ftm_sa_ota_url_write(uint8_t* buf, int32_t url_len) {
	bool result = false;

	if (buf && url_len > 0) {
		g_ftm_devinfo.ota_url_len = url_len - sizeof(g_ftm_devinfo.ota_port);
		memcpy(g_ftm_devinfo.ota_url, buf, g_ftm_devinfo.ota_url_len);;
		g_ftm_devinfo.ota_url[g_ftm_devinfo.ota_url_len] = 0;

		g_ftm_devinfo.ota_port = (buf[g_ftm_devinfo.ota_url_len] << 8) | (buf[g_ftm_devinfo.ota_url_len + 1] & 0xff);

		FTM_LOG("WRITE: url(%s), port(%d), len(0x%02x - %d)", g_ftm_devinfo.ota_url, g_ftm_devinfo.ota_port, url_len, url_len);

		devinfo_store();

		result = true;
	}

	return result;
}
#endif

/****************************FA functions**************************************/
static bool is_http_active(uint8_t sim, int cid) {
	if (!ql_datacall_get_sim_profile_is_active(sim, cid)) {

		if (ql_network_register_wait(sim, 10) != QL_DATACALL_SUCCESS) {
			FTM_LOG_ERR("ql_network_register_wait fail");
			return false;
		}

		if ((ql_set_data_call_asyn_mode(sim, cid, 0)) != QL_DATACALL_SUCCESS) {
			FTM_LOG_ERR("ql_set_data_call_asyn_mode fail");
			return false;
		}

		if (ql_start_data_call(sim, cid, QL_PDP_TYPE_IP, "uninet", NULL, NULL, 0) != QL_DATACALL_SUCCESS) {
			FTM_LOG_ERR("ql_start_data_call fail");
			return false;
		}
	}

	return true;
}

static int http_write_cb(int* client, void* argv, char* data, int size, uint8_t end) {
	if (data && size) {
		g_ftm_http_upload_success = strstr(data, "success") ? true : false;
		FTM_LOG("=== Upload FA result %s ===", (g_ftm_http_upload_success ? "success" : "fail"));
	}
	return 0;
}

static void http_event_cb(int* client, int event, int event_code, void* argv) {
	if (!argv) {
		FTM_LOG_ERR("if (!argv)");
		return;
	}

	http_client* h_client = (http_client*)argv;

	if (*client != h_client->http_cli) {
		return;
	}

	switch (event) {

	case HTTP_EVENT_SESSION_ESTABLISH:
	{
		//HTTP 会话连接建立
		if (event_code != QL_HTTP_OK) {
			ql_rtos_semaphore_release(semaphore_handler);
		}
	}
	break;

	case HTTP_EVENT_RESPONE_STATE_LINE:
	{
		//接收 HTTP 响应
	}
	break;

	case HTTP_EVENT_SESSION_DISCONNECT:
	{
		//HTTP 会话连接断开
		ql_rtos_semaphore_release(semaphore_handler);
	}
	break;
	}
}

static int http_request(http_client* client, char* url) {
	char dload_range[50] = { 0 };

	//注网拨号
	if (!is_http_active(client->sim_id, client->profile_idx)) {
		FTM_LOG_ERR("!is_http_active, return -1");
		return -1;
	}

	//创建新的http请求句柄
	if (ql_httpc_new(&(client->http_cli), http_event_cb, client) != QL_HTTP_OK) {
		FTM_LOG_ERR("ql_httpc_new(http_event_cb) != QL_HTTP_OK");
		ql_httpc_release(&(client->http_cli));
		return -2;
	}

	//设置http请求方式为HTTP_METHOD_GET
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_METHOD, HTTP_METHOD_GET);

	//文件大小不为0, 设置断点续传
	if (client->b_is_http_range) {
		client->http_progress.dload_size = client->http_progress.file_size;
		sprintf(dload_range, "Range: bytes=%d-", client->http_progress.file_size);
		ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_REQUEST_HEADER, dload_range);
	}

	//设置url、sim_id、cid
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_URL, url);
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_SIM_ID, client->sim_id);
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_PDPCID, client->profile_idx);

	//接收回应(cb & param)
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_WRITE_FUNC, http_write_cb);
	ql_httpc_setopt(&(client->http_cli), HTTP_CLIENT_OPT_WRITE_DATA, client);

	//发送http请求
	if (ql_httpc_perform(&client->http_cli) == QL_HTTP_OK) {
		//等待信号量
		if (ql_rtos_semaphore_wait(semaphore_handler, QL_WAIT_FOREVER) != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("ql_rtos_semaphore_wait(semaphore_handler) != QL_OSI_SUCCESS");
			ql_httpc_release(&(client->http_cli));
			return -1;
		}

		ql_httpc_release(&(client->http_cli));
		return 0;
	}

	ql_httpc_release(&(client->http_cli));
	return -3;
}

static void http_init(http_client* client) {
	ql_rtos_semaphore_create(&semaphore_handler, 0);
	memset(client, 0x00, sizeof(http_client));

	client->http_cli = 0;
	client->profile_idx = 1;
	client->sim_id = 0;
	client->e_stage = FOTA_HTTP_DOWN_INIT;
	client->i_save_size = 0;
	client->b_is_have_space = true;
	client->http_progress.is_show = false;
	client->last_precent = 0;
	client->chunk_encode = 0;
}

static bool ftm_fa_result_upload(uint8_t* sn) {
	uint8_t try_times = 0;
	http_client  client;

	char imei[IEMI_LEN + 1] = { 0 };
	char url[200] = { 0 };

	g_ftm_http_upload_success = false;

	ql_dev_get_imei(imei, IEMI_LEN, SIM_INDEX);

	sprintf(url, FTM_SRV_URL"?sn=%s&result=1&imei=%s", sn, imei);

	FTM_LOG("START ...");
	http_init(&client);
	http_request(&client, url);

	while (1) {
		if (try_times++ > 60 || g_ftm_http_upload_success) {
			break;
		}

		DELAYms(100);
	}

	FTM_LOG("END(%s), tryTimes: %d, URL: %s", (g_ftm_http_upload_success ? "success" : "fail"), try_times, url);

	return g_ftm_http_upload_success;
}

#if HAS_MP3
static bool ftm_write_mp3_file(pkt_t* pkt) {
	bool result = false;
	uint32_t off = 0;

	static uint8_t* mp3_buf = NULL;
	static uint32_t mp3_len = 0;

	if (pkt->data_len > 0) {
		uint8_t transfer = pkt->data[off++];

		FTM_LOG(" %s", (transfer == 0x04 ? "Start" : (transfer == 0x05 ? "Writing" : "End")));

		if (transfer == 0x04) {
			// 传输开始 [len, name, start, end, data]
			uint8_t fileLen = pkt->data[off++];
			uint8_t fileName[100] = { 0 };
			memcpy(fileName, &pkt->data[off], fileLen);
			off += fileLen;

			FTM_LOG("fileName = %s", fileName);
		}

		// 传输中 [start, end, data]
		uint32 start = (pkt->data[off] << 8) | (pkt->data[off + 1]);
		uint32 end = (pkt->data[off + 2] << 8) | (pkt->data[off + 3]);
		uint32 LEN = (end - start + 1);

		FTM_LOG("Read: %d([0x%02x%02x, 0x%02x%02x])", LEN, pkt->data[off], pkt->data[off + 1], pkt->data[off + 2], pkt->data[off + 3]);

		if (end > start) {
			if (!mp3_buf) {
				// 目前所知的mp3文件就两个(9k、38k), 其实最好的办法是把总长度传输过来
				mp3_buf = (uint8_t*)malloc(50 * 1024);
			}


			if (mp3_len + LEN < 50 * 1024) {
				off += 4;
				memcpy(mp3_buf + mp3_len, &pkt->data[off], LEN);
				mp3_len += LEN;

				result = true;
			}
		}

		if (transfer == 0x06) {
			// 传输结束[start, end, data, crc]
			result = false;

			off += LEN;
			extern uint32_t data_crc32(const uint8_t * buf, uint32_t size);
			uint32_t crc = (pkt->data[off] << 24) | (pkt->data[off + 1] << 16) | (pkt->data[off + 2] << 8) | (pkt->data[off + 3]);
			uint32_t crc_32 = data_crc32(mp3_buf, mp3_len);

			FTM_LOG("Check crc - [%d(0x%02x%02x%02x%02x), %d]", crc, pkt->data[off], pkt->data[off + 1], pkt->data[off + 2], pkt->data[off + 3], crc_32);

			if (crc == crc_32) {
				// CRC校验合格后写入文件系统
				FTM_LOG("CRC ok, write to file system");

				QFILE fd = -1;
				if ((fd = ql_fopen(FTM_MP3_FILENAME, "wb+")) > 0) {
					result = ql_fwrite(mp3_buf, mp3_len, 1, fd) == mp3_len;
					ql_fclose(fd);

					FTM_LOG("WRITE RESULT: %s", (result ? "success" : "fail"));
				}
			}

			if (!mp3_buf) {
				free(mp3_buf);
				mp3_buf = NULL;
			}
		}
	}

	return result;
}

static bool ftm_play_mp3() {
	extern void ql_ext_codec_cb_init(void);
	extern void Play_Signal_Mp3(char* pfilename);

	if (ql_file_exist(FTM_MP3_FILENAME) != QL_FILE_OK) {
		return false;
	}

	// 初始化运放
	if (ql_pin_set_func(AUDIO_PA_PIN, AUDIO_PA_FUNC) == QL_GPIO_SUCCESS) {
		ql_gpio_init(GPIO_0, GPIO_OUTPUT, PULL_NONE, LVL_HIGH);
	}

	// A05无内置codec，只需初始化iic
	// ql_ext_codec_cb_init();
	ql_pin_set_func(QUEC_PIN_DNAME_I2C_M2_SCL, 0);
	ql_pin_set_func(QUEC_PIN_DNAME_I2C_M2_SDA, 0);
	ql_I2cInit(i2c_2, STANDARD_MODE);

	ql_set_audio_path_speaker();
	ql_aud_set_volume(QL_AUDIO_PLAY_TYPE_LOCAL, AUDIOHAL_SPK_VOL_11);

	Play_Signal_Mp3(FTM_MP3_FILENAME);
	return true;
}
#endif

static bool ftm_get_wifi_status(void) {
	return g_wifi_status;
}

static void ftm_set_wifi_status(bool status) {
	g_wifi_status = status;
}

static uint8_t ftm_get_gprs_rssi(uint8_t* buf) {
	uint8_t data_len = 0;
	buf[data_len++] = g_gprs_rssi;

	return data_len;
}

static void ftm_set_gprs_rssi(int8_t rssi) {
	g_gprs_rssi = rssi;
}

static void ftm_fa_upload(bool test_ok, bool is_dtu, uint8_t* sn) {
	if (test_ok && ftm_fa_result_upload(sn)) {
		/* 1, 上传成功 */
		FTM_LOG("1, UPLOAD OK, SN = %s, is_dtu = %d", sn, is_dtu);

		/* 2, Cat1切到work模式 */
		FTM_LOG("2, change Cat1 to Work mode");
		ftm_bootmode_write(MODE_WK, CAT1_IS_MASTER);

		if (!CAT1_IS_MASTER) {
			// 3, 如果M0是mater的话, 则要先发送命令给M0 (让M0切换到Work并重启)
			FTM_LOG("3, SEND CMD TO M0, change to Work mode also, and reboot");

			uint8 buf[] = { MODE_WK };
			SEND_RESPONSE(FTM_M0_REPORT_FA_RESULT, buf, sizeof(buf));
		}
	} else {
		uint8 buf[] = { 0 };
		SEND_RESPONSE(FTM_M0_REPORT_FA_RESULT, buf, sizeof(buf));
	}
}

static void master_rx_flush(void) {
	receive_length = 0;
}

/*
* Description: 	uart(M0 & PC)的接收数据的callback
* Parameter:
* @data:		数据内容
* Return：
* Others:
*/
static void master_rx_cb(uint8_t data) {
	if (receive_length + 1 < RECV_BUF_SIZE) {
		receive_buffer[receive_length++] = data;
	}
}

/*
* Description: 	master接收包的处理
* Parameter:
* @pkt:			解析包(cmd)
* Return：
* Others:
*/
static void master_packet_process(pkt_t* pkt) {
	uint8_t buf[512] = { 0 };
	uint8_t data_len = 0;

	uint8_t cmd_type = (pkt->data_len == 0) ? CMD_GET : CMD_SET;

	FTM_LOG("cmd_type = %s, pkt->cmd = 0x%02x, pkt->data_len = %d", (cmd_type == CMD_GET ? "READ" : "WRITE"), pkt->cmd, pkt->data_len);

	switch (pkt->cmd) {

#ifdef FTM_M0_INFO
	case FTM_M0_INFO:
		// PC工具直接发命令到Cat1, 要求检查M0的sw (先转发至M0)
		businiss_pkt_send(CAT1_QUERY_M0_INFO, NULL, 0);
		break;
#endif

	case CAT1_QUERY_M0_INFO:
#ifdef FTM_M0_INFO
		if (CAT1_IS_MASTER) {
			// 返还结果给PC
			data_len = ftm_sa_cmd01(pkt->data, data_len, buf);
			SEND_RESPONSE(FTM_M0_INFO, buf, data_len);
		} else {
#endif
			// 处理跟随问题(M0 boot mode)
			g_ftm_sync_rsp = 0xFF;
			// 澳柯玛系列: 利用原有的cmd01(dtu_cmd01), bootmode = rsp.bootmode
			typedef struct {
				unsigned char ctrl_state;
				uint8_t bootmode;
				char ble_mac[6];
				char ble_uuid[16];
				uint32_t m0_sw_ver;
				uint32_t cat1_reset_stage;
				uint32_t cat1_reset_time;
			} saCmd01RspPkg;

			saCmd01RspPkg info = { 0 };
			memcpy(&info, pkt->data, sizeof(saCmd01RspPkg));

			FTM_LOG("Cat1: %s, M0: %s(%d), %s", g_ftm_devinfo.bootmode <= MODE_SA ? "SA" : (g_ftm_devinfo.bootmode <= MODE_FA ? "FA" : "WORK"), info.bootmode <= MODE_SA ? "SA" : (info.bootmode <= MODE_FA ? "FA" : "WORK"), info.bootmode, g_ftm_devinfo.bootmode != info.bootmode ? "Need sync" : "Do nothing");
			if (g_ftm_devinfo.bootmode != info.bootmode) {
				ftm_bootmode_write(info.bootmode, true);
			}
#ifdef FTM_M0_INFO
		}
#endif	
		break;

	case FTM_485_TEST:
#if HAS_485_UART
		if (cmd_type == CMD_GET) {
			FTM_LOG("PC or other 485 device send command, response back");
			// PC工具通过485口发过来的, 再通过485口发出去 .
			// 收到: {0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x43, 0x00, 0x68, 0xBB}
			// 发送: {0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x43, 0x01, 0x01, 0x6A, 0xBB}
			SEND_RESULT(pkt->cmd, true);
		} else {
			// 其他485工装返回过来的结果, 说明485测试OK
			uint8_t rsp = pkt->data[0];
			FTM_LOG("HAS_485_UART, DUT RESPONE RESULT: %d", rsp);

			if (!g_uart_485[1].exist) {
				// 没有第二路485, 那么工装回复01或03给第一路, 都可以认为成功
				if (g_uart_485[0].exist && (rsp == 0x01 || rsp == 0x03)) {
					g_uart_485[0].result = true;
				}
			} else {
				if (g_uart_485[0].exist && rsp == 0x01) {
					// 收到1: {0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x43, 0x01, 0x01, 0x6A, 0xBB}
					g_uart_485[0].result = true;
				} else if (g_uart_485[1].exist && rsp == 0x03) {
					// 收到2: {0xAA, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x43, 0x01, 0x03, 0x6C, 0xBB}
					g_uart_485[1].result = true;
				}
			}
		}
#else 
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_SN:
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_sn_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_sn_write(pkt->data, pkt->data_len));
		}
		break;

	case FTM_HW:
		data_len = ftm_sa_hw_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_SW:
		data_len = ftm_sa_sw_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_BT_UUID:
#if HAS_UUID
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_ble_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_ble_write(pkt->data, pkt->data_len));
		}
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_BOOTMODE:
		if (cmd_type == CMD_GET) {
			data_len = ftm_bootmode_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_bootmode_write(pkt->data[0], false));
		}
		break;

	case FTM_SIM_ICCID:
		data_len = ftm_sa_sim_iccid_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_SIM_NUM:
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_sim_num_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_sim_num_write(pkt->data, pkt->data_len));
		}
		break;

	case FTM_SIM_IMSI:
		data_len = ftm_sa_sim_imsi_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_IMEI:
		data_len = ftm_sa_imei_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_WIFI_BT_MAC:
		data_len = ftm_sa_wifi_bt_mac_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_OTA_URL_CONFIG:
#if HAS_OTA_URL
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_ota_url_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_ota_url_write(pkt->data, pkt->data_len));
		}
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_WRITE_MP3:
#if HAS_MP3
		SEND_RESULT(pkt->cmd, ftm_write_mp3_file(pkt));
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_GPRS_STATUS:
		data_len = ftm_get_gprs_rssi(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_WIFI_STATUS:
		SEND_RESULT(pkt->cmd, ftm_get_wifi_status());
		break;

	case FTM_SPI_TEST:
#if HAS_SPI
		SEND_RESULT(pkt->cmd, ftm_sa_spi());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_NTC_CAL:
#if HAS_NTC_CAL
		SEND_RESULT(pkt->cmd, ftm_sa_ntc_cal());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_PLAY_MP3:
#if HAS_MP3
		SEND_RESULT(pkt->cmd, ftm_play_mp3());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_M0_REPORT_FA_RESULT:
#if HAS_485_UART
		FTM_LOG("HAS 485 UART, NEED check 485 function with another 485 device before upload FA result");
		// 有x路并测试OK
		FTM_LOG("485 TEST %s, upload FA result", (g_uart_485[0].result + g_uart_485[1].result == g_uart_485[0].exist + g_uart_485[1].exist ? "OK" : "FAIL"));
		ftm_fa_upload(g_uart_485[0].result + g_uart_485[1].result == g_uart_485[0].exist + g_uart_485[1].exist, false, g_ftm_devinfo.sn);
#else 
		FTM_LOG("No 485 UART, upload FA result directly !");
		ftm_fa_upload(pkt->data[0] == FA_TEST_OK, false, g_ftm_devinfo.sn);
#endif
		break;

	default:
		break;
	}
}

/*
* Description: 	接收包的handler (M0 || PC)
* Parameter:
* @rx_buffer:	接收数据buffer
* @rx_length:	接收数据长度
* Return：		0: 未接收完成; -1: 接收错误, 清除; 1, 接收完成, 处理后清除
* Others:
*/
int8_t ftm_master_rx_handler(uint8_t* rx_buffer, uint16_t rx_length) {
	if (rx_length == 0) {
		return 0;
	}

	int8_t pkt_result = 0;
	pkt_t pkt = { 0 };

	FTM_HEX_BUF_LOG("rx_buffer", rx_buffer, rx_length);

	uint8_t pkt_prefix[] = PKT_PREFIX;

	if (!memcmp(rx_buffer, pkt_prefix, sizeof(pkt_prefix))) {
		// 1, FTM: PC[经M0转]发到Cat1的数据
		FTM_LOG("(1), FTM's CMD: PC [to M0] send to Cat1");
		pkt_result = get_pc_pkt(rx_buffer, rx_length, &pkt);
	} else {
		// 2, WORK: M0发过来的数据
		FTM_LOG("(2), WORK's CMD: M0 to Cat1");
		pkt_result = get_business_pkt(rx_buffer, rx_length, &pkt);
	}

	if (pkt_result > 0) {
		master_packet_process(&pkt);
		master_rx_flush();
	}

	return pkt_result;
}


static uint16_t wifi_scan_start(ql_wifi_ap_info_s ApInfos[]) {
	uint16_t cnt = 0;

	if (QL_WIFISCAN_SUCCESS != ql_wifiscan_option_set(QL_WIFI_SCAN_DEFAULT_AP_CNT, QL_WIFI_SCAN_DEFAULT_ROUND, QL_WIFISCAN_CHANNEL_ALL, QL_WIFI_SCAN_DEFAULT_TIME)) {
		FTM_LOG_ERR("ql_wifiscan_option_set failed");
		return cnt;
	}

	// open
	if (QL_WIFISCAN_SUCCESS != ql_wifiscan_open()) {
		FTM_LOG_ERR("ql_wifiscan_open failed");
		return cnt;
	}

	// begin scan
	if (QL_WIFISCAN_SUCCESS != ql_wifiscan_do(&cnt, ApInfos)) {
		FTM_LOG("ql_wifiscan_do failed");
		return cnt;
	}

#if 0
	FTM_LOG("ql_wifiscan_do(&cnt, ApInfos): cnt: %d", cnt);
	for (int i = 0; i < cnt; i++) {
		FTM_LOG("SSID: %s, MAC: %x%x, RSSI: %d, CHANNEL: %d", ApInfos[i].ssid, ApInfos[i].bssid_high, ApInfos[i].bssid_low, ApInfos[i].rssival, ApInfos[i].channel);
	}
#endif

	// close
	if (QL_WIFISCAN_SUCCESS != ql_wifiscan_close()) {
		FTM_LOG("ql_wifiscan_close failed");
		return cnt;
	}

	return cnt;
}

#if HAS_UUID
static void ble_scan_notify(void* ind_msg_buf, void* ctx) {
	int ret;
	ql_event_t* evt = (ql_event_t*)ind_msg_buf;;
	if (evt == NULL)
		return;

	int status = (int)evt->param1;

	switch (evt->id) {

	case QUEC_BT_START_STATUS_IND:
	{
		if (status != 0) return;
		FTM_LOG("BLE Start Success!");

		ret = ql_bt_ble_set_localname(ftm_ble_name);
		if (ret != QL_BT_SUCCESS) {
			FTM_LOG("ql_bt_ble_set_localname, ret: %d", ret);
		}

		ret = ql_bleadv_set_param(ftm_ble_adv_param);
		if (ret != QL_BT_SUCCESS) {
			FTM_LOG("ql_bleadv_set_param, ret: %d", ret);
		}

		ret = ql_bleadv_set_data(ftm_ble_adv_data);
		if (ret != QL_BT_SUCCESS) {
			FTM_LOG("ql_bleadv_set_data, ret: %d", ret);
		}

		ret = ql_bleadv_start();
		if (ret != QL_BT_SUCCESS) {
			FTM_LOG("ql_bleadv_start, ret: %d", ret);
		}
	}
	break;

	case QUEC_BT_STOP_STATUS_IND:
		FTM_LOG("BLE Stop !");
		break;

	case QUEC_BT_BLE_RESET_IND:
		FTM_LOG("BLE Reset !");
		break;

	default:
		break;
	}
}
#endif

static void wifi_test_thread(void* arg) {
	uint16_t cnt = 0;
	ql_wifi_ap_info_s ApInfos[QL_WIFI_SCAN_DEFAULT_AP_CNT] = { 0 };

	while (1) {
		if (cnt == 0) {
			cnt = wifi_scan_start(ApInfos);
		}

		if (ftm_get_wifi_status() != (cnt > 0 ? 1 : 0)) {
			FTM_LOG("Success, set Wifi Status: %d", cnt > 0 ? 1 : 0);
		}
		ftm_set_wifi_status(cnt > 0);

#if HAS_UUID
		// in wifi scan gap, start BLE broadcast
		int ret = 0;
		// 如果还没有分配UUID, 略过
		if (g_ftm_devinfo.ble_major != 0 && g_ftm_devinfo.ble_major != BLE_MAJOR_DEFAULT) {
			FTM_LOG("Switch from wifi scan to ble broadcast");

			// 预先加载广播内容信息, 9是初始长度, 需要初始化装载
			if (ftm_ble_adv_data.date_len == 9) {
				memcpy(ftm_ble_adv_data.data + ftm_ble_adv_data.date_len, g_ftm_devinfo.ble_uuid, sizeof(g_ftm_devinfo.ble_uuid));
				ftm_ble_adv_data.date_len += sizeof(g_ftm_devinfo.ble_uuid);

				ftm_ble_adv_data.data[ftm_ble_adv_data.date_len++] = (g_ftm_devinfo.ble_major >> 8) & 0xff;
				ftm_ble_adv_data.data[ftm_ble_adv_data.date_len++] = g_ftm_devinfo.ble_major & 0xff;

				ftm_ble_adv_data.data[ftm_ble_adv_data.date_len++] = (g_ftm_devinfo.ble_minor >> 8) & 0xff;
				ftm_ble_adv_data.data[ftm_ble_adv_data.date_len++] = g_ftm_devinfo.ble_minor & 0xff;

				ftm_ble_adv_data.data[ftm_ble_adv_data.date_len++] = 0xB6;

				FTM_LOG("BLE BROADCAST DATA LEN = %d", ftm_ble_adv_data.date_len);
				FTM_HEX_BUF_LOG("BLE BROADCAST", ftm_ble_adv_data.data, ftm_ble_adv_data.date_len);
			}

			ret = ql_ble_gatt_server_init(ble_scan_notify);
			if (ret != QL_BT_SUCCESS) {
				FTM_LOG_ERR("ql_ble_gatt_server_init failed, code: %d, ret");
			}

			ret = ql_bt_start();
			if (ret != QL_BT_SUCCESS && ret != QL_BT_ALREADY_STARTED_ERR) {
				FTM_LOG_ERR("ql_bt_start failed, code: %d, ret");
			}
		}
#endif

		// control next scan time(不管有没有扫到wifi, 不管下一步是否要关闭蓝牙, 都sleep 2s)
		DELAYms(2 * 1000);

#if HAS_UUID
		if (cnt == 0) {
			// 如果wifi没有扫描到, 则需要先关闭蓝牙, 把时间留给wifi; 反之, 则不用关闭蓝牙了, 全部运行开启蓝牙上
			// stop BLE broadcast, prepare next wifi scan
			if (g_ftm_devinfo.ble_major != 0 && g_ftm_devinfo.ble_major != BLE_MAJOR_DEFAULT) {
				ql_bt_stop();
				ql_ble_gatt_server_release();
				FTM_LOG("Switch from ble broadcast to wifi scan");
			}
		}

		// must delay, or wifi function will fail!!! -- 200ms会概率失败，改到1000ms
		DELAYms(1000);
#endif
	}
}

/*
* Description: 	解析gprs 信息
* Parameter:
* @cell_info:	传入的cell信息(由ql_nw_get_cell_info获取)
* Return：		返回信号值: 默认-128
* Others:		RSSI说明：
				-40到-50之间为在基站附近，手机信号畅快无阻；
				-50到-70之间信号属于良好，正常情况不会影响上网；
				-70到-90之间属于信号较弱，偶尔会出现上网卡顿情况(对于Cat1来说, 通信属于正常)；
				-90以下基本只能通讯状态；还有一丝通讯信号存在；
				如果你手机信号处于-100到-110之间，基本没信号，打电话困难，上网更别提了。
*/
static int8_t gprs_pkg_parse(ql_nw_cell_info_s* cell_info) {
	int8_t rssi = BASE_RSSI_MIN;

	if (cell_info) {
		if (cell_info->gsm_info_valid) {
			for (int i = 0; i < cell_info->gsm_info_num && cell_info->gsm_info[i].flag == 0; i++) {
				if (cell_info->gsm_info[i].mcc && cell_info->gsm_info[i].lac && cell_info->gsm_info[i].cid) {
					rssi = MAX(cell_info->gsm_info[i].rssi, BASE_RSSI_MIN);
					FTM_LOG("Success, set GPRS(GSM) RSSI: %d(0x%02x)", rssi, (rssi & 0xff));
					break;
				}
			}
		} else if (cell_info->lte_info_valid) {
#if 0
			FTM_LOG("ql_nw_get_cell_info(): cnt: %d", cell_info->lte_info_num);
			for (int i = 0; i < cell_info->lte_info_num; i++) {
				FTM_LOG("MCC: %d, TAC: %d, CID: %d, RSSI: %d, flag: %d", cell_info->lte_info[i].mcc, cell_info->lte_info[i].tac, cell_info->lte_info[i].cid, cell_info->lte_info[i].rssi, cell_info->lte_info[i].flag);
			}
#endif
			for (int i = 0; i < cell_info->lte_info_num && cell_info->lte_info[i].flag == 0; i++) {
				if (cell_info->lte_info[i].mcc && cell_info->lte_info[i].tac && cell_info->lte_info[i].cid) {
					rssi = MAX(cell_info->lte_info[i].rssi, BASE_RSSI_MIN);
					FTM_LOG("Success, set GPRS(LTE) RSSI: %d(0x%02x)", rssi, (rssi & 0xff));
					break;
				}
			}
		}
	}

	return rssi;
}

static void gprs_test_thread(void* arg) {
	ql_nw_errcode_e errcode = QL_NW_EXECUTE_ERR;
	ql_nw_cell_info_s cell_info = { 0 };

	while (1) {
		if (errcode != QL_NW_SUCCESS) {
			if ((errcode = ql_nw_get_cell_info(SIM_INDEX, &cell_info)) == QL_NW_SUCCESS) {
				ftm_set_gprs_rssi(gprs_pkg_parse(&cell_info));
			}
		}

		DELAYms(3 * 1000);
	}
}

#if !CAT1_IS_MASTER
static void sync_mode_thread(void* arg) {
	while (1) {
		DELAYms(2 * 1000);
		if (g_ftm_sync_rsp < 5) {
			g_ftm_sync_rsp++;
			businiss_pkt_send(CAT1_QUERY_M0_INFO, NULL, 0);
		}
	}
}
#endif

#if CAT1_IS_MASTER && HAS_NTC_CAL
extern int get_temp(float* pfl_temp);
//#include "ql_pin_cfg.h" 涉及到修改CMakeLists
#if (defined QL_APP_PROJECT_DEF_EC800G) || (defined QL_APP_PROJECT_DEF_EG800G)
#define QL_PIN_NET_STATUS          16
#define QL_PIN_NET_STATUS_FUNC     5
#elif defined QL_APP_PROJECT_DEF_EC600G
#define QL_PIN_NET_STATUS          54
#define QL_PIN_NET_STATUS_FUNC     5
#elif defined QL_APP_PROJECT_DEF_EG700G
#define QL_PIN_NET_STATUS          45
#define QL_PIN_NET_STATUS_FUNC     1
#elif defined QL_APP_PROJECT_DEF_EC200G
#define QL_PIN_NET_STATUS          6
#define QL_PIN_NET_STATUS_FUNC     0
#endif

static void led_init() {
	ql_pin_set_func(QL_PIN_NET_STATUS, QL_PIN_NET_STATUS_FUNC);
	ql_pwm_open(QL_PWM_10);
}

void led_enable(int period, int ontime) {
	ql_pwm_cfg_s pwm_cfg;
	pwm_cfg.clk_sel = QL_CLK_XTAL_LP;
	pwm_cfg.prescaler = 26000 - 1;
	pwm_cfg.period = period - 1;
	pwm_cfg.duty = (ontime * pwm_cfg.period) / period;
	ql_pwm_enable(QL_PWM_10, &pwm_cfg);
}

static void ntc_cal_thread(void* arg) {
	float temp = 0;
	int ret = 0;

	led_init();
	//DELAYms(2 * 1000);

	// 1, 慢闪
	led_enable(500, 200);

	while (1) {
		DELAYms(2 * 1000);
		ret = get_temp(&temp);
		if (ret == 0 && ROUND10((int)(temp * 10)) == 0) {
			// 检查ntc温度为0度
			FTM_LOG("Test Success");

			// 2, 常亮
			led_enable(500, 500);
			DELAYms(1 * 1000);

			// 3, 上传FA结果
			ftm_fa_upload(true, false, g_ftm_devinfo.sn);
		}
	}
}
#endif

static void ql_uart_notify_cb(uint32 ind_type, ql_uart_port_number_e port, uint32 size) {
	uint8_t* recv_buff = calloc(1, QL_UART_RX_BUFFER_SIZE + 1);
	if (recv_buff == NULL) {
		return;
	}

	uint32_t real_size = 0;
	int read_len = 0;

	switch (ind_type) {

	case QUEC_UART_RX_OVERFLOW_IND:
	case QUEC_UART_RX_RECV_DATA_IND:
	{
		while (size > 0) {
			memset(recv_buff, 0, QL_UART_RX_BUFFER_SIZE + 1);
			real_size = MIN(size, QL_UART_RX_BUFFER_SIZE);
			read_len = ql_uart_read(port, recv_buff, real_size);

			// FTM_LOG("PORT = %d, read_len = %d", port, read_len);
			// FTM_HEX_BUF_LOG("rx_buffer", recv_buff, read_len);

			for (int i = 0; i < read_len; i++) {
				master_rx_cb(recv_buff[i]);
			}

			if ((read_len > 0) && (size >= read_len)) {
				size -= read_len;
			} else {
				break;
			}
		}
	}
	break;

	case QUEC_UART_TX_FIFO_COMPLETE_IND:
	{
#if HAS_485_UART && FTM_485_CTL
		if (port == FTM_PC_UART_PORT) {
			ql_gpio_set_level(FTM_485_GPIO, LVL_HIGH);
		}
#endif	
	}
	break;
	}

	free(recv_buff);
	recv_buff = NULL;
}

static void ql_uart_thread(void* param) {
	uart_config config = *(uart_config*)param;

	ql_uart_config_s uart_cfg = {
		.baudrate = config.baudrate,
		.flow_ctrl = QL_FC_NONE,
		.data_bit = QL_UART_DATABIT_8,
		.stop_bit = QL_UART_STOP_1,
		.parity_bit = QL_UART_PARITY_NONE
	};

	if (ql_uart_set_dcbconfig(config.port, &uart_cfg) == QL_UART_SUCCESS) {
#if CAT1_IS_MASTER || HAS_485_UART
		if (config.port == FTM_PC_UART_PORT) {
			/***********************************************************
			Note start:
				1. If QL_UART_PORT_1 is selected for use, there is no need to set TX and RX pin and function
				2. According to the QuecOpen GPIO table, user should select the correct PIN to set function
				3. CTS and RTS pins (UART2 and UART3) also need to be initialized if hardware flow control function is required
			************************************************************/
			ql_pin_set_func(FTM_PC_UART_TX_PIN, FTM_PC_UART_TX_FUNC);
			ql_pin_set_func(FTM_PC_UART_RX_PIN, FTM_PC_UART_RX_FUNC);
			/*Note end*/

#if HAS_485_UART && FTM_485_CTL
			ql_pin_set_func(FTM_485_CTL, FTM_485_CTRL_FUNC);
			ql_gpio_init(FTM_485_GPIO, GPIO_OUTPUT, PULL_NONE, LVL_HIGH);
			FTM_LOG("HAS_485_UART, INIT FTM_485_GPIO = HIGH");
#endif
			// FTM_LOG("Note: A *** User should select the correct PIN to set function, except QL_UART_PORT_1 ***");
		}
#endif

		if (ql_uart_open(config.port) == QL_UART_SUCCESS) {
			ql_uart_register_cb(config.port, ql_uart_notify_cb);

#if HAS_485_UART
			bool is_fa_mode = is_ftm_fa_mode();
#endif

			while (1) {
				DELAYms(1 * 1000);
#if HAS_485_UART
				if (is_fa_mode && config.port == FTM_PC_UART_PORT) {
					// fa模式下, 持续发送485数据, 直到接收到正确的回应
					if (!(g_uart_485[0].result + g_uart_485[1].result == g_uart_485[0].exist + g_uart_485[1].exist)) {
						FTM_LOG("Revolution is not yet successful, comrades still need to work hard");
						SEND_RESPONSE(FTM_485_TEST, NULL, 0);
					}
				}
#endif
			}
		}
	}

	ql_rtos_task_delete(NULL);
}

static void ftm_process_thread(void* pvParameters) {
	while (1) {
		DELAYms(50);
		if (ftm_master_rx_handler(receive_buffer, receive_length) < 0) {
			master_rx_flush();
		}
	}
}

static void create_threads(uint8_t ftm_mode) {
	QlOSStatus err = QL_OSI_SUCCESS;

	ql_task_t dtu_rx_task_m0 = NULL;
	ql_task_t dtu_process_task = NULL;

#if !CAT1_IS_MASTER
	ql_task_t sync_mode_task = NULL;
#endif

	ql_task_t wifi_task = NULL;
	ql_task_t gprs_task = NULL;

#if CAT1_IS_MASTER && HAS_NTC_CAL
	ql_task_t ntc_cal_task = NULL;
#endif

	static uart_config uart1 = {
		FTM_MCU_UART_PORT,
		FTM_MCU_UART_BAUD
	};

	/* Note: Cat1是主芯片同时又有485, 这里的波特率怎么处理? */
#if (CAT1_IS_MASTER || HAS_485_UART)
	ql_task_t dtu_rx_task_pc = NULL;
	static uart_config uart3 = {
		FTM_PC_UART_PORT,
#if HAS_485_UART
		FTM_485_UART_BAUD
#else
		FTM_PC_UART_BAUD
#endif	
	};
#endif

	if (ftm_mode) {
		/* 1, M0发送过来的数据 (QL_UART_PORT_1): 工作模式不能打开Uart0, 会跟dtu里的open uart0冲突 */
		err = ql_rtos_task_create(&dtu_rx_task_m0, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "FTM_QUART_1", ql_uart_thread, (void*)&uart1, QL_UART_EVENT_CNT);
		if (err != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("created ql_rtos_task_create ql_uart_thread failed err: [%d]", err);
			return;
		} else {
			FTM_LOG("1, ql_uart_thread(uart: 1) success");
		}
	}

#if (CAT1_IS_MASTER || HAS_485_UART)
	/* 2, PC直接发送过来的数据 (QL_UART_PORT_3) */
	err = ql_rtos_task_create(&dtu_rx_task_pc, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "FTM_QUART_3", ql_uart_thread, (void*)&uart3, QL_UART_EVENT_CNT);
	if (err != QL_OSI_SUCCESS) {
		FTM_LOG_ERR("created ql_rtos_task_create ql_uart_thread failed err: [%d]", err);
		return;
	} else {
		FTM_LOG("2, ql_uart_thread(uart: 3) success");
	}
#endif

	/* 3, 处理uart(1 || 2 || 3)的数据 */
	err = ql_rtos_task_create(&dtu_process_task, QL_UART_STACK_SIZE * 2, APP_PRIORITY_NORMAL, "FTM_QDTU_RX", ftm_process_thread, NULL, QL_UART_EVENT_CNT);
	if (err != QL_OSI_SUCCESS) {
		FTM_LOG_ERR("created ql_rtos_task_create ftm_process_thread failed err: [%d]", err);
		return;
	} else {
		FTM_LOG("3, ftm_process_thread() success");
	}

	if (ftm_mode) {
		/* 4, 获取wifi的状态 */
		err = ql_rtos_task_create(&wifi_task, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "WIFI_TEST", wifi_test_thread, NULL, QL_UART_EVENT_CNT * 2);
		if (err != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("ql_rtos_task_create wifi_test_thread failed err: [%d]", err);
			return;
		} else {
			FTM_LOG("4, wifi_test_thread() success");
		}

		/* 5, 获取gprs的状态和值 */
		err = ql_rtos_task_create(&gprs_task, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "GPRS_TEST", gprs_test_thread, NULL, QL_UART_EVENT_CNT * 2);
		if (err != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("ql_rtos_task_create gprs_test_thread failed err: [%d]", err);
			return;
		} else {
			FTM_LOG("5, gprs_test_thread() success");
		}
	}

#if !CAT1_IS_MASTER
	if (ftm_mode) {
		/* 6, 与同步M0同步模式 */
		err = ql_rtos_task_create(&sync_mode_task, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "MODE_SYNC", sync_mode_thread, NULL, QL_UART_EVENT_CNT);
		if (err != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("ql_rtos_task_create sync_mode_thread failed err: [%d]", err);
			return;
		} else {
			FTM_LOG("6, sync_mode_thread() success");
		}
	}
#endif

#if CAT1_IS_MASTER && HAS_NTC_CAL
	if (is_ftm_fa_mode()) {
		/* 7, 仅有Cat1且需要NTC校准的, 则要进行FA测试 */
		err = ql_rtos_task_create(&ntc_cal_task, QL_UART_STACK_SIZE, APP_PRIORITY_NORMAL, "NTC_CAL", ntc_cal_thread, NULL, QL_UART_EVENT_CNT);
		if (err != QL_OSI_SUCCESS) {
			FTM_LOG_ERR("ql_rtos_task_create ntc_cal_thread failed err: [%d]", err);
			return;
		} else {
			FTM_LOG("7, ntc_cal_thread() success");
		}
	}
#endif
}

bool ftm_start(void) {
	devinfo_load();

	bool ftm_mode = is_ftm_mode();

	/* 读Cat1版本号, 不然每次都用串口命令去查询 */
	ftm_sa_sw_read(NULL);

#if !CAT1_THREAD_RESIDENT
	if (!ftm_mode) {
		return false;
	} else {
#endif

		// 加个对Major的特殊处理吧(作为ftm是否有效的标记, 业务可以使用其他值比如sim卡信息等, 所以暂时不能去除)
		// [2024-05-15] 业务代码已经去除用ble_major作为ftm文件是否有效的判断, 后续同步代码时, 可以把以下补丁删除 !
		if (g_ftm_devinfo.bootmode == MODE_FA) {
			if (g_ftm_devinfo.ble_major == BLE_MAJOR_DEFAULT) {
				g_ftm_devinfo.ble_major = 0;
				FTM_LOG("Here write major to 0x0000");
				devinfo_store();
			}
		}

		create_threads(ftm_mode);
		return ftm_mode;
#if !CAT1_THREAD_RESIDENT
	}
#endif
}
